package com.fho4565.commands;

import com.fho4565.core.DisplayList;
import com.fho4565.core.DisplayObject;
import com.fho4565.core.DisplayObjectType;
import com.fho4565.main.Configs;
import com.fho4565.main.Utils;
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.ComponentArgument;
import net.minecraft.commands.arguments.ObjectiveArgument;
import net.minecraft.network.chat.*;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.apache.commons.lang3.tuple.MutablePair;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Objects;

import static com.fho4565.main.Utils.sendCdFeedback;
import static com.fho4565.main.Utils.sendTCdFeedback;
import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import static com.mojang.brigadier.arguments.StringArgumentType.string;
import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
import static net.minecraft.commands.arguments.ObjectiveArgument.getObjective;

@Mod.EventBusSubscriber
public class DisplayCommand {
    private static final TypeToken<DisplayList> type = new TypeToken<>() {};
    public static DisplayList displayList;
    static class ComponentAdapter implements JsonSerializer<Component>, JsonDeserializer<Component> {
        @Override
        public JsonElement serialize(Component src, Type typeOfSrc, JsonSerializationContext context) {
            return Component.Serializer.toJsonTree(src);
        }
        @Override
        public Component deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            return Component.Serializer.fromJson(json);
        }
    }
    private static void save() {
        Configs.displayList.set(new GsonBuilder()
                .registerTypeAdapter(Component.class, new ComponentAdapter())
                .create()
                .toJson(displayList));
        Configs.displayList.save();
    }

    private static void read() {
        String json = Configs.displayList.get();
        if (json.isEmpty()) {
            displayList = new DisplayList();
            return;
        }
        DisplayList map = new GsonBuilder()
                .registerTypeAdapter(Component.class, new ComponentAdapter())
                .create().fromJson(json, type.getType());
        displayList = Objects.requireNonNullElseGet(map, DisplayList::new);
    }

    public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
        dispatcher.register(
                literal("display").requires(s -> s.hasPermission(2))
                        .then(add())
                        .then(remove())
                        .then(list())
                        .then(modify())
                        .then(clear())
                        .then(literal("showAll").executes(context -> {
                            if (displayList.isEmpty()){
                                return 0;
                            }
                            displayList.forEach(displayObject -> displayObject.setEnabled(true));
                            sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.showAll");
                            return 1;
                        }))
        );
    }

    private static LiteralArgumentBuilder<CommandSourceStack> remove() {
        return literal("remove")
                .then(argument("name", string())
                        .suggests((context, builder) -> {
                            ArrayList<MutablePair<String, String>> suggestions = new ArrayList<>();
                            displayList.forEach(displayObject -> suggestions.add(new MutablePair<>(displayObject.getName(), displayObject.getObjective())));
                            return Utils.getCommandSuggestions(builder, suggestions);
                        })
                        .executes(context -> {
                            String name = getString(context, "name");
                            if (!displayList.contains(name)) {
                                return 0;
                            }
                            displayList.remove(name);
                            save();
                            sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.remove", name);
                            return 1;
                        }));
    }

    private static LiteralArgumentBuilder<CommandSourceStack> modify() {
        return literal("modify")
                .then(literal("json")
                        .then(argument("name", string())
                                .suggests((context, builder) -> {
                                    ArrayList<MutablePair<String, String>> suggestions = new ArrayList<>();
                                    displayList.stream().filter(displayObject -> displayObject.getType() == DisplayObjectType.JSON).forEach(displayObject -> suggestions.add(new MutablePair<>(displayObject.getName(), displayObject.getObjective())));
                                    return Utils.getCommandSuggestions(builder, suggestions);
                                })
                                .then(literal("json")
                                        .then(argument("json", ComponentArgument.textComponent())
                                                .executes(context -> {
                                                            String name = getString(context, "name");
                                                            if (!displayList.contains(name)) {
                                                                return 0;
                                                            }
                                                            displayList.getByName(name).setJson(ComponentArgument.getComponent(context, "json"));
                                                            save();
                                                            sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.modify.json", name);
                                                            return 1;
                                                        }
                                                )
                                        )
                                )
                                .then(literal("enabled")
                                        .then(argument("isEnabled", BoolArgumentType.bool())
                                                .executes(context -> {
                                                            String name = getString(context, "name");
                                                            if (!displayList.contains(name)) {
                                                                return 0;
                                                            }
                                                            displayList.getByName(name).setEnabled(BoolArgumentType.getBool(context, "isEnabled"));
                                                            save();
                                                    sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.modify.enabled", name);
                                                    return 1;
                                                        }
                                                )
                                        )
                                )
                                .then(literal("position")
                                        .then(argument("x", IntegerArgumentType.integer())
                                                .then(argument("y", IntegerArgumentType.integer())
                                                        .executes(context -> {
                                                                    String name = getString(context, "name");
                                                                    if (!displayList.contains(name)) {
                                                                        return 0;
                                                                    }
                                                                    displayList.getByName(name).setPosition(getInteger(context, "x"), getInteger(context, "y"));
                                                            sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.modify.position", name);

                                                            save();
                                                                    return 1;
                                                                }
                                                        )
                                                )

                                        )
                                )
                                /*.then(literal("players")
                                        .then(argument("players", EntityArgument.players())
                                                .executes(context -> {
                                                            String name = getString(context, "name");
                                                            if (!displayList.contains(name)) {
                                                                return 0;
                                                            }
                                                            HashSet<String> names = new HashSet<>();
                                                            EntityArgument.getPlayers(context, "players").forEach(player -> names.add(player.getName().getString()));
                                                            displayList.getByName(name).setPlayers(names);
                                                            save();
                                                            return 1;
                                                        }
                                                )
                                        )
                                )*/
                        )
                )
                .then(literal("objective")
                        .then(argument("name", string())
                                .suggests((context, builder) -> {
                                    ArrayList<MutablePair<String, String>> suggestions = new ArrayList<>();
                                    displayList.stream().filter(displayObject -> displayObject.getType() == DisplayObjectType.SCOREBOARD).forEach(displayObject -> suggestions.add(new MutablePair<>(displayObject.getName(), displayObject.getObjective())));
                                    return Utils.getCommandSuggestions(builder, suggestions);
                                })
                                .then(literal("objective")
                                        .then(argument("objective", string())
                                                .executes(context -> {
                                                            String name = getString(context, "name");
                                                            if (!displayList.contains(name)) {
                                                                return 0;
                                                            }
                                                            displayList.getByName(name).setObjective(getString(context, "objective"));
                                                            save();
                                                            sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.modify.objective", name);
                                                            return 1;
                                                        }
                                                )
                                        )
                                )
                                .then(literal("enabled")
                                        .then(argument("isEnabled", BoolArgumentType.bool())
                                                .executes(context -> {
                                                            String name = getString(context, "name");
                                                            if (!displayList.contains(name)) {
                                                                return 0;
                                                            }
                                                            displayList.getByName(name).setEnabled(BoolArgumentType.getBool(context, "isEnabled"));
                                                            save();
                                                    sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.modify.enabled", name);

                                                    return 1;
                                                        }
                                                )
                                        )
                                )
                                .then(literal("position")
                                        .then(argument("x", IntegerArgumentType.integer())
                                                .then(argument("y", IntegerArgumentType.integer())
                                                        .executes(context -> {
                                                                    String name = getString(context, "name");
                                                                    if (!displayList.contains(name)) {
                                                                        return 0;
                                                                    }
                                                                    displayList.getByName(name).setPosition(getInteger(context, "x"), getInteger(context, "y"));
                                                                    save();
                                                            sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.modify.position", name);

                                                            return 1;
                                                                }
                                                        )
                                                )

                                        )
                                )
                                /*.then(literal("players")
                                        .then(argument("players", EntityArgument.players())
                                                .executes(context -> {
                                                            String name = getString(context, "name");
                                                            if (!displayList.contains(name)) {
                                                                return 0;
                                                            }
                                                            HashSet<String> names = new HashSet<>();
                                                            EntityArgument.getPlayers(context, "players").forEach(player -> names.add(player.getName().getString()));
                                                            displayList.getByName(name).setPlayers(names);
                                                            save();
                                                            return 1;
                                                        }
                                                )
                                        )
                                )*/
                        )
                );
    }

    private static LiteralArgumentBuilder<CommandSourceStack> add() {
        return literal("add")
                .then(literal("json")
                        .then(argument("name", string())
                                .then(argument("x", IntegerArgumentType.integer())
                                        .then(argument("y", IntegerArgumentType.integer())
                                                .then(argument("json", ComponentArgument.textComponent())
                                                        .executes(context -> {
                                                            if(displayList.contains(getString(context, "name"))){
                                                                sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.modify.add.exists",true, getString(context, "name"));
                                                                return 0;
                                                            }
                                                            Component json = ComponentArgument.getComponent(context, "json");
                                                            displayList.add(new DisplayObject(getString(context, "name"),
                                                                    json,
                                                                            getInteger(context, "x"), getInteger(context, "y"),
                                                                            DisplayObjectType.JSON));
                                                                    save();
                                                            sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.modify.add",getString(context, "name"));
                                                            return 1;
                                                                }
                                                        )
                                                )
                                        )
                                )
                        )
                )
                .then(literal("objective")
                        .then(argument("name", string())
                                .then(argument("x", IntegerArgumentType.integer())
                                        .then(argument("y", IntegerArgumentType.integer())
                                                .then(argument("objective", ObjectiveArgument.objective())
                                                        .executes(context -> {
                                                            if(displayList.contains(getString(context, "name"))){
                                                                sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.modify.add.exists",true, getString(context, "name"));
                                                                return 0;
                                                            }
                                                                    displayList.add(new DisplayObject(getString(context, "name"),
                                                                            getObjective(context, "objective").getName(),
                                                                            getInteger(context, "x"), getInteger(context, "y"),
                                                                            DisplayObjectType.SCOREBOARD));
                                                                    save();
                                                            sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.modify.add", getString(context, "name"));
                                                                    return 1;
                                                                }
                                                        )
                                                )
                                        )
                                )
                        )
                );
    }

    private static LiteralArgumentBuilder<CommandSourceStack> clear() {
        return literal("clear")
                .executes(context -> {
                    if (displayList.isEmpty()) {
                        return 0;
                    }
                    displayList.forEach(displayObject -> displayObject.setEnabled(false));
                    sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.clearAll");
                    return 1;
                })
                .then(argument("name", string())
                        .suggests((context, builder) -> {
                            ArrayList<MutablePair<String, String>> suggestions = new ArrayList<>();
                            displayList.stream().filter(displayObject -> !displayObject.isEnabled()).forEach(displayObject -> suggestions.add(new MutablePair<>(displayObject.getName(), displayObject.getObjective())));
                            return Utils.getCommandSuggestions(builder, suggestions);
                        })
                        .executes(context -> {
                            String name = getString(context, "name");
                            if (!displayList.contains(name)) {
                                sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.clearnull");
                                return 0;
                            }
                            displayList.getByName(name).setEnabled(false);
                            sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.clear", name);
                            return 1;
                        })
                );
    }

    private static LiteralArgumentBuilder<CommandSourceStack> list() {
        return literal("list").executes(context -> {
            if (displayList.isEmpty()) {
                sendTCdFeedback(context, "mcd.com.fho4565.command.displayObject.listnull");
                return 1;
            }
            displayList.forEach(displayObject -> {
                switch (displayObject.getType()) {
                    case JSON -> {
                        MutableComponent mutableComponent = new TranslatableComponent("mcd.com.fho4565.command.displayObject.list",
                                displayObject.getName(),displayObject.getPosition().getLeft(),displayObject.getPosition().getRight(),
                                displayObject.isEnabled())
                                .withStyle(Style.EMPTY.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,displayObject.getJson())));
                        sendCdFeedback(context, mutableComponent);
                    }
                    case SCOREBOARD -> {
                        MutableComponent mutableComponent = new TranslatableComponent("mcd.com.fho4565.command.displayObject.list",
                                displayObject.getName(),displayObject.getPosition().getLeft(),displayObject.getPosition().getRight(),
                                displayObject.isEnabled())
                                .withStyle(Style.EMPTY.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,new TextComponent(displayObject.getObjective()))));
                        sendCdFeedback(context, mutableComponent);
                    }
                }
            });
            return 1;
        });
    }

    @SubscribeEvent
    public static void onLoad(WorldEvent.Load event) {
        read();
    }
}
